Skip to content

KAFKA-12170: Fix for Connect Cast SMT to correctly transform a Byte array into a string#9950

Merged
mimaison merged 9 commits into
apache:trunkfrom
sknop:kafka-12170
Mar 3, 2021
Merged

KAFKA-12170: Fix for Connect Cast SMT to correctly transform a Byte array into a string#9950
mimaison merged 9 commits into
apache:trunkfrom
sknop:kafka-12170

Conversation

@sknop

@sknop sknop commented Jan 22, 2021

Copy link
Copy Markdown
Contributor

Cast SMT transformation for bytes -> string.
Without this fix, the conversion becomes

ByteBuffer.toString(), which always gives one of these useless results:
"java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]"
"[B@5ec0a365" (for byte array)

With this change, the byte array is converted into a hex string of the
byte buffer content, for example

"FEDCBA9876543210"

Completed with test case and successfully tried out in a
real database conversion.

Committer Checklist (excluded from commit message)

  • [X ] Verify design and implementation
  • [ X] Verify test coverage and CI build status
  • Verify documentation (including upgrade notes)

sknop added 2 commits January 10, 2021 17:52
Cast SMT transformation for bytes -> string.
Without this fix, the conversion becomes

ByteBuffer.toString(), which always gives this useless result:
    "java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]"

With this change, the byte array is converted into a hex string of the
byte buffer content, for example

    "FEDCBA9876543210"

Completed with test case and successfully tried out in a
real database conversion.
It turns out that not all connectors produce a HeapBuffer to store bytes.
SAP Hana, specifically, just stores a simple byte array that was not
captured by the cast transformer.

Completed with test case.

Fix for https://issues.apache.org/jira/browse/KAFKA-12170
@g1geordie

Copy link
Copy Markdown
Contributor

@sknop hello , It seems like there are some style error .
You can use ./gradlew checkstyleMain checkstyleTest to cehck .

@sknop

sknop commented Jan 25, 2021

Copy link
Copy Markdown
Contributor Author

@sknop hello , It seems like there are some style error .
You can use ./gradlew checkstyleMain checkstyleTest to cehck .

Thank you @g1geordie for your feedback. I have adjusted the code and it now passes the style check.

@mimaison mimaison left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!
It looks good overall, I just left a few minor comments

Comment thread connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Cast.java Outdated
Comment thread connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Cast.java Outdated
@sknop

sknop commented Feb 18, 2021 via email

Copy link
Copy Markdown
Contributor Author

@kkonstantine kkonstantine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @sknop
Left a few comments inline.

Comment thread connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Cast.java Outdated
private static String castByteArrayToString(byte[] array) {
StringBuilder sbuf = new StringBuilder();
for (byte b : array) {
sbuf.append(String.format("%02X", b));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm a bit curious. Why are we selecting hex here and not base64 for example?
Hex of course is less efficient.
https://issues.apache.org/jira/browse/KAFKA-12170 does not mention anything about the rational, so I thought I'd ask.

@rhauch wdyt about the representation here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I suggested Hex representation was that in the use case I encountered - database PK field encoded as BINARY retrieved via CDC Source Connector - the representation in the SQL CLI was Hex, so choosing the same format made it easy to compare source and target.

I would suggest that base64 encoding would validate its own dedicated transformer, with an ability to choose padding, for example

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in my previous comment, I agree with @kkonstantine that base64 would be preferable. Doing that would align better with the existing Values class used in Connect's header converter mechanism.

If we want to add support for multiple encodings, we would need to have a KIP since it would likely mean changing the SMT configuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If base64 is the better choice we shall go with that. Would we need to adjust the JIRA ticket to reflect this?

I just ran a little test in the JShell:

jshell> byte[] byteArray = new byte[] {(byte) 0xFE, (byte) 0xDC, (byte) 0xBA, (byte) 0x98, 0x76, 0x54, 0x32, 0x10};
byteArray ==> byte[8] { -2, -36, -70, -104, 118, 84, 50, 16 }

jshell> Base64.getEncoder().encode(byteArray)
$2 ==> byte[12] { 47, 116, 121, 54, 109, 72, 90, 85, 77, 104, 65, 61 }

jshell> Base64.getEncoder().encodeToString(byteArray)
$3 ==> "/ty6mHZUMhA="

jshell> Base64.getEncoder().withoutPadding().encodeToString(byteArray)
$4 ==> "/ty6mHZUMhA"

Would you suggest using padding or not?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connect's Values code just uses Base64.getEncoder().encodeToString((byte[]) value) and does not disable padding. Seems like we should follow the same pattern here. Thoughts, @kkonstantine ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. encodeToString is the method I had in mind. Used in Values and JsonConverter for a conversion of bytes to string. It's also more efficient.

…rray.

If the ByteBuffer does not have a backing array, we use get(byte[])
to extract the content.
@rhauch

rhauch commented Feb 22, 2021

Copy link
Copy Markdown
Contributor

Before this fix, the changes made in #4820 (KAFKA-6684) resulted in toString() being called on byte[] and ByteBuffer. As highlighted in this PR and issue that ByteBuffer.toString() is not useful, but the toString() on byte[] still works. This PR seems to change that behavior, which would not be backward compatible.

The discussion on PR #4820 also talked about making this compatible with Values.convertToString(...), which for byte[] and ByteBuffer results in a base 64 encoded string (with ISO-8859-1 encoding). See the Values code for details.

Because of this, it seems much more sensible to also use base64 here for ByteBuffer so it matches the existing behavior with byte[].

Actually, the existing code simply outputs the toString() result of a byte[] object, which is of the form [B@22ef9844, which is the Object.toString() implementation that includes the class alias (e.g., [B is a byte array) and the hex representation of the object's hashCode().

However, given that none of the cast forms of byte[] or ByteBuffer[] are useful, then perhaps it's worth adding the previous discussion on #4820 mentioning that:

As @ewencp suggested, for consistency perhaps we can use the same string formats as SimpleHeaderConverter: https://github.com/apache/kafka/blob/trunk/connect/api/src/main/java/org/apache/kafka/connect/data/Values.java,

As mentioned above, the Values.convertToString(...) uses a base 64 encoding. And this aligns with @kkonstantine's suggestion:

Why are we selecting hex here and not base64 for example? Hex of course is less efficient.

I agree that it maybe doesn't suffice in all user situations, so if we also want to support other encodings we'd need other config changes that will require using the KIP mechanism to propose such enhancements.

WDYT?

Comment thread connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Cast.java Outdated
private static String castByteArrayToString(byte[] array) {
StringBuilder sbuf = new StringBuilder();
for (byte b : array) {
sbuf.append(String.format("%02X", b));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in my previous comment, I agree with @kkonstantine that base64 would be preferable. Doing that would align better with the existing Values class used in Connect's header converter mechanism.

If we want to add support for multiple encodings, we would need to have a KIP since it would likely mean changing the SMT configuration.

@kkonstantine kkonstantine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed comments @rhauch
@sknop I agree with @rhauch that base64 would be preferable and consistent with our previous choices. I also agree to punt support of multiple encodings for now.

private static String castByteArrayToString(byte[] array) {
StringBuilder sbuf = new StringBuilder();
for (byte b : array) {
sbuf.append(String.format("%02X", b));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. encodeToString is the method I had in mind. Used in Values and JsonConverter for a conversion of bytes to string. It's also more efficient.

This matches the internal format used in other parts of the Connect code.

@rhauch rhauch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One import-related fix, but otherwise looks good.

Comment thread connect/transforms/src/main/java/org/apache/kafka/connect/transforms/Cast.java Outdated
@rhauch
rhauch self-requested a review February 25, 2021 16:38
Change requested by code review.
@sknop

sknop commented Feb 26, 2021

Copy link
Copy Markdown
Contributor Author

What about documentation?

Where is the documentation stored and what is the process to change it? It might be useful to point out that the Cast SMT will create base64 strings for byte arrays.

@rhauch

rhauch commented Feb 26, 2021

Copy link
Copy Markdown
Contributor

The documentation is auto-generated from the transforms code, via TransformationDoc. Since this is an existing SMT, there are no changes that that TransformationDoc class, but we still have to change the Cast class here and here to mention that byte arrays can be cast to strings.

@sknop

sknop commented Feb 27, 2021

Copy link
Copy Markdown
Contributor Author

Thank you @rhauch. Just to make sure I understand this correctly - the types mentioned in the cast string are the target casts, not the source tasks. The current code does not allow casting to a byte array, only casting from a byte array.
As I understand it, therefore no documentation changes are required (except maybe for release notes)?

@rhauch

rhauch commented Mar 2, 2021

Copy link
Copy Markdown
Contributor

If we don't clarify the documentation, then I think users will be very confused. Can you take a stab at improving/expanding the documentation a bit to clarify the input and output types?

@sknop

sknop commented Mar 2, 2021

Copy link
Copy Markdown
Contributor Author

Yes of course, totally agree.

@rhauch rhauch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @sknop! Looks great.

@mimaison, you have a "request changes", so can you please review? Thanks!

@mimaison mimaison left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. LGTM

@kkonstantine kkonstantine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM
Thanks @sknop

@mimaison

mimaison commented Mar 3, 2021

Copy link
Copy Markdown
Member

Test failures are not related:

JDK 8 / org.apache.kafka.connect.mirror.integration.MirrorConnectorsIntegrationSSLTest.testOneWayReplicationWithAutoOffsetSync()
JDK 11 / org.apache.kafka.connect.integration.RebalanceSourceConnectorsIntegrationTest.testMultipleWorkersRejoining

Merging to trunk

@mimaison
mimaison merged commit f06a47a into apache:trunk Mar 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants